/*******************************************************************************************
Copyright (C) 2016 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************/

/*globals date2ISOString, gc, mainWindow, nmr, settings, Application, BinaryStream, FileDialog, Dir, File, NMRPredictorParams, NMRSpectrum, MnUi, MessageBox, TextStream, ProgressDialog*/

/*jslint plusplus: true, continue: true, indent: 4*/

function SiemensSpectro_Engine(aParameters) {
	"use strict";

	//Instance attributes
	this.f2Width = aParameters.f2Width;
	this.f2LowestFrequency = aParameters.f2LowestFrequency;
	this.f2Frequency = aParameters.f2Frequency;
	this.f2Nucleus = aParameters.f2Nucleus;

	this.f1Width = aParameters.f1Width;
	this.f1LowestFrequency = aParameters.f1LowestFrequency;
	this.f1Frequency = aParameters.f1Frequency;
	this.f1Nucleus = aParameters.f1Nucleus;


//------------------------------INSTANCE METHODS-------------------------------------
	SiemensSpectro_Engine.prototype.getCurrentDate = function () {

		return date2ISOString(new Date());
	};

	SiemensSpectro_Engine.prototype.newDocument = function (aDocument) {

		var document;

		if (aDocument === undefined || aDocument.uuid === "" || aDocument.itemCount() > 0) {
			document = mainWindow.newDocument();
		} else {
			document = aDocument;
		}

		return document;
	};

	SiemensSpectro_Engine.prototype.validateParameters = function () {

		var result, logDetail;

		result = {};
		result.status = SiemensSpectro_Engine.status.PASSED;
		logDetail = "";

		if (this.f2Width === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 width parameter is mandatory and has not been specified";
		} else if (isNaN(this.f2Width)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 width parameter (" + this.f2Width + ") is not a valid number";
		} else {
			this.f2Width = Number(this.f2Width);
			if (this.f2Width === 0) {
				result.status = SiemensSpectro_Engine.status.FAILED;
				logDetail += "\n\t\t[ERROR]: The f2 width parameter value must not be 0";
			}
		}

		if (this.f2LowestFrequency  === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 lowest frequency parameter is mandatory and has not been specified";
		} else if (isNaN(this.f2LowestFrequency)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 lowest frequency parameter (" + this.f2LowestFrequency  + ") is not a valid number";
		}

		if (this.f2Frequency === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 frequency parameter is mandatory and has not been specified";
		} else if (isNaN(this.f2Frequency)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 frequency parameter (" + this.f2Frequency + ") is not a valid number";
		} else {
			this.f2Frequency = Number(this.f2Frequency);
			if (this.f2Frequency === 0) {
				result.status = SiemensSpectro_Engine.status.FAILED;
				logDetail += "\n\t\t[ERROR]: The f2 frequency parameter value must not be 0";
			}
		}

		if (this.f2Nucleus === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 nucleus parameter is mandatory and has not been specified";
		} else if (!SiemensSpectro_Engine.isValidNucleus(this.f2Nucleus)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f2 nucleus (" + this.f2Nucleus + ") is not a valid nucleus: available nucelus are: " + SiemensSpectro_Engine.validNucleus;
		}

		if (this.f1Width === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 width parameter is mandatory and has not been specified";
		} else if (isNaN(this.f1Width)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 width parameter (" + this.f1Width + ") is not a valid number";
		} else {
			this.f1Width = Number(this.f1Width);
			if (this.f1Width === 0) {
				result.status = SiemensSpectro_Engine.status.FAILED;
				logDetail += "\n\t\t[ERROR]: The f1 width parameter value must not be 0";
			}
		}

		if (this.f1LowestFrequency  === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 lowest frequency parameter is mandatory and has not been specified";
		} else if (isNaN(this.f1LowestFrequency)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 lowest frequency parameter (" + this.f1LowestFrequency  + ") is not a valid number";
		}

		if (this.f1Frequency === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 frequency parameter is mandatory and has not been specified";
		} else if (isNaN(this.f1Frequency)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 frequency parameter (" + this.f1Frequency + ") is not a valid number";
		} else {
			this.f1Frequency = Number(this.f1Frequency);
			if (this.f1Frequency === 0) {
				result.status = SiemensSpectro_Engine.status.FAILED;
				logDetail += "\n\t\t[ERROR]: The f1 frequency parameter value must not be 0";
			}
		}

		if (this.f1Nucleus === undefined) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 nucleus parameter is mandatory and has not been specified";
		} else if (!SiemensSpectro_Engine.isValidNucleus(this.f1Nucleus)) {
			result.status = SiemensSpectro_Engine.status.FAILED;
			logDetail += "\n\t\t[ERROR]: The f1 nucleus (" + this.f1Nucleus + ") is not a valid nucleus: available nucelus are: " + SiemensSpectro_Engine.validNucleus;
		}

		return result;
	};

	SiemensSpectro_Engine.prototype.createSpectrum = function (af2Size, af1Size, aFilename) {

		var result, f2Spectrum, f1Spectrum, spectrum;

		result = {};
		result.status = SiemensSpectro_Engine.status.PASSED;
		result.log = "";

		f2Spectrum = {};
		f2Spectrum.nucleus = this.f2Nucleus;
		f2Spectrum.frequency = this.f2Frequency;
		f2Spectrum.fromPPM = this.f2LowestFrequency / this.f2Frequency;
		f2Spectrum.toPPM = (this.f2Width + this.f2LowestFrequency) / this.f2Frequency;
		f2Spectrum.size = af2Size;

		f1Spectrum = {};
		f1Spectrum.nucleus = this.f1Nucleus;
		f1Spectrum.frequency = this.f1Frequency;
		f1Spectrum.fromPPM = this.f1LowestFrequency / this.f1Frequency;
		f1Spectrum.toPPM = (this.f1Width + this.f1LowestFrequency) / this.f1Frequency;
		f1Spectrum.size = af1Size;

		//Create the spectrum object using the object above
		spectrum = new NMRSpectrum({
			1: {
				nucleus: f1Spectrum.nucleus,
				frequency: f1Spectrum.frequency,
				from_ppm: f1Spectrum.fromPPM,
				to_ppm: f1Spectrum.toPPM,
				size: af1Size
			},
			2: {
				nucleus: f2Spectrum.nucleus,
				frequency: f2Spectrum.frequency,
				from_ppm: f2Spectrum.fromPPM,
				to_ppm: f2Spectrum.toPPM,
				size: af2Size
			},
			filename: aFilename,
			origin: 'Spectro file',
			title: ''
		}
			);

		if (spectrum.uuid === undefined) {
			result.log = "\n\t[ERROR]: The 2D spectrum creation process has failed";
			result.status = SiemensSpectro_Engine.status.FAILED;
		} else {
			result.spectrum = spectrum;
			spectrum.update();
		}

		return result;
	};

	SiemensSpectro_Engine.prototype.process = function (aFilesArray) {

		var i, result, auxResult, np, file, filename, document,
			values, flagCreated, proc, progress, realData, imgData;

		result = {};
		document = mainWindow.activeDocument;
		result.status = SiemensSpectro_Engine.status.PASSED;
		result.log = "[INFO]: Siemens Spectro imported process started on " + this.getCurrentDate();
		realData = [];
		imgData = [];

		auxResult = this.validateParameters();
		if (auxResult.status) {
			mainWindow.setBusy("Loading data. Please wait...");
			document = this.newDocument(document);
			//Sort the increment files by using the spit function in order to access quickly to 8th and 9th characters from end of file name
			aFilesArray.sort(function (a, b) { return a.split("_")[1] - b.split("_")[1]; });
			progress = new ProgressDialog();
			progress.labelText = "Loading data. Please wait...";
			progress.minimum = 0;
			progress.maximum = aFilesArray.length - 1;
			for (i = 0; i < aFilesArray.length; i++) {
				progress.value = i;
				filename = aFilesArray[i];
				file = new File(filename);
				if (file.exists) {
					values = SiemensSpectro_Engine.readComplexDataFromBinaryFile(filename);
					if (!flagCreated) {
						auxResult = this.createSpectrum(values.real.length, aFilesArray.length, filename);
						np = values.real.length;
						if (auxResult.status === SiemensSpectro_Engine.status.FAILED) {
							result.log += auxResult.log;
							result.status = auxResult.status;
							mainWindow.endBusy();
							break;
						}
						flagCreated = true;
					}

					realData.push(values.real);
					imgData.push(values.imag);
				} else {
					result.log += "\n\t[ERROR]: The following file does not exist: " + filename;
					result.status = SiemensSpectro_Engine.status.WITH_ERRORS;
				}
				if (progress.wasCanceled) {
					result.status = SiemensSpectro_Engine.status.CANCELLED;
					result.log = "\n\t[ERROR]: Siemens Spectro import was cancelled by user";
					mainWindow.endBusy();
					return result;
				}
			}

			if (flagCreated) {
				if (realData.length > 1 && imgData.length > 1) {
					nmr.beginFidModification(auxResult.spectrum);
					if (auxResult.spectrum.fidSetReal("all", "all", realData)) {
						result.log += "\n\t[INFO]: Real data added to the spectrum sucessfully";
					} else {
						result.log += "\n\t[ERROR]: There was a problem adding the real data to the spectrum";
						result.status = SiemensSpectro_Engine.status.WITH_ERRORS;
					}

					if (auxResult.spectrum.fidSetImag("all", "all", imgData)) {
						result.log += "\n\t[INFO]: Imaginary data added to the spectrum sucessfully";
					} else {
						result.log += "\n\t[ERROR]: There was a problem adding the imaginary data to the spectrum";
						result.status = SiemensSpectro_Engine.status.WITH_ERRORS;
					}

					nmr.endFidModification(auxResult.spectrum);
					realData = [];
					imgData = [];
					gc();

				} else {
					result.log += "\n\t[ERROR]: No data found to set to the spectrum";
				}
			} else {
				result.log += "\n\t[ERROR]: Impossible add data to the spectrum due to the spectrum was not created";
				result.status = SiemensSpectro_Engine.status.WITH_ERRORS;
			}

			proc = auxResult.spectrum.proc;
			proc.setParameter("DC.Apply", true);
			proc.setParameter("LP[2].size", 2 * np);
			proc.setParameter("Apodization[2].SineII2.Apply", true);
			proc.setParameter("Apodization[2].FP.Apply", true);
			proc.setParameter("Apodization[2].FP.Value", 0.5);
			proc.setParameter("FT[2].Quadrature", true);
			proc.setParameter("FT[2].Invert", true);
			proc.setParameter("PC[2].Method", "Magnitude");
			proc.setParameter("LP[1].size", 2 * aFilesArray.length);
			proc.setParameter("Apodization[1].SineII2.Apply", true);
			proc.setParameter("FT[1].Quadrature", true);
			proc.setParameter("FT[1].Invert", true);
			auxResult.spectrum.proc = proc;

			auxResult.spectrum.process();
			auxResult.spectrum.update();
			auxResult.spectrum.fitToHeight();
			document.update();
			mainWindow.processEvents();
			result.log += "\n[INFO]: Siemens Spectro imported process finished on " + this.getCurrentDate();
			mainWindow.endBusy();
		} else {
			result.log += "\n\t[ERROR]: The parameter validation process has failed and the process has been aborted due to the following reasons: " + auxResult.log;
			result.status = auxResult.status;
		}

		return result;
	};
}


//------------------------------STATIC METHODS-------------------------------------
SiemensSpectro_Engine.isValidNucleus = function (aNucleus) {
	"use strict";

	var i, result;

	result = false;
	for (i = 0; i < SiemensSpectro_Engine.validNucleus.length; i++) {
		if (aNucleus === SiemensSpectro_Engine.validNucleus[i]) {
			result = true;
			break;
		}
	}

	return result;
};

SiemensSpectro_Engine.readComplexDataFromBinaryFile = function (aPath) {
	"use strict";

	var stream, data, file, readObject, isRe;

	readObject = {real: [], imag: []};
	file = new File(aPath);
	if (file.exists) {
		file.open(File.ReadOnly);
		stream = new BinaryStream(file);
		stream.endianness = 1;
		isRe = true;
		do {
			data = Number(stream.readReal32());
			if (isRe) {
				readObject.real.push(data);
				isRe = false;
			} else {
				readObject.imag.push(data);
				isRe = true;
			}
		} while (!stream.atEnd());
		file.close();
	}
	return readObject;
};

SiemensSpectro_Engine.writeDataToFile = function (aFilePath, aValue, aAppend) {
	"use strict";

	var file, stream;

	file = new File(aFilePath);
	if (file.exists && aAppend !== undefined && aAppend) {
		file.open(File.Append);
	} else {
		if (file.exists) {
			File.remove(aFilePath);
		}
		File.create(aFilePath);
		file.open(File.WriteOnly);
	}
	stream = new TextStream(file);
	stream.codec = "UTF-8";
	stream.write(aValue);
	stream.flush();
	file.close();
};

//------------------------------STATIC ATTRIBUTES-------------------------------------
SiemensSpectro_Engine.status = {
	FAILED: 0,
	PASSED: 1,
	WITH_ERRORS: 2,
	CANCELLED: 3
};

SiemensSpectro_Engine.validNucleus = ["1H", "13C", "11B", "15N", "17O", "19F", "29Si", "31P"];


function SiemensSpectro_Viewer() {
	"use strict";

	var filenames, dialog, showDialog, data, lastDir, dir, spectroExtension, engine, parameters, results,
		lastDirectoryKey = "Siemens Spectro/Last Directory",
		f2WidthKey = "Siemens Spectro/f2 Width",
		f2LowestFrequencyKey = "Siemens Spectro/f2 Lowest Frequency",
		f2FrequencyKey = "Siemens Spectro/f2 Frequency",
		f2NucleusKey = "Siemens Spectro/f2 Nucleus",
		f1WidthKey = "Siemens Spectro/f1 Width",
		f1LowestFrequencyKey = "Siemens Spectro/f1 Lowest Frequency",
		f1FrequencyKey = "Siemens Spectro/f1 Frequency",
		f1NucleusKey = "Siemens Spectro/f1 Nucleus";

	//Load GUI by using a ui file
	dialog = Application.loadUiFile("ricares:importSiemensSpectro.ui");
	lastDir = Dir.home();
	spectroExtension = ".Spectro";

	//Load values from registry
	data =  settings.value(f2WidthKey, "");
	if (data !== "") {
		dialog.widgets.spbxf2Width.value = data;
	}
	data =  settings.value(f2LowestFrequencyKey, "");
	if (data !== "") {
		dialog.widgets.spbxf2LowestFrequency.value = data;
	}
	data =  settings.value(f2FrequencyKey, "");
	if (data !== "") {
		dialog.widgets.spbxf2Frequency.value = data;
	}
	data =  settings.value(f2NucleusKey, "");
	if (data !== "") {
		dialog.widgets.lef2Nucleus.text = data;
	}

	data =  settings.value(f1WidthKey, "");
	if (data !== "") {
		dialog.widgets.spbxf1Width.value = data;
	}
	data =  settings.value(f1LowestFrequencyKey, "");
	if (data !== "") {
		dialog.widgets.spbxf1LowestFrequency.value = data;
	}
	data =  settings.value(f1FrequencyKey, "");
	if (data !== "") {
		dialog.widgets.spbxf1Frequency.value = data;
	}
	data =  settings.value(f1NucleusKey, "");
	if (data !== "") {
		dialog.widgets.lef1Nucleus.text = data;
	}

	data = settings.value(lastDirectoryKey, "");
	if (data !== "") {
		lastDir = data;
	}
	lastDir = FileDialog.getExistingDirectory(lastDir, "Select an import directory with .Spectro files");
	if (lastDir === "") {
		return;
	}

	//Execute dialog
	showDialog = true;
	while (showDialog) {

		//Check if cancle button is pulsed by user
		if (!dialog.exec()) {
			return;
		}

		if (dialog.widgets.spbxf2Width.value === 0) {
			dialog.widgets.spbxf2Width.setFocus();
			MessageBox.critical("The f2 width parameter value must not be 0");
			continue;
		}

		if (dialog.widgets.spbxf2Frequency.value === 0) {
			dialog.widgets.spbxf2Frequency.setFocus();
			MessageBox.critical("The f2 frequency value must not be 0");
			continue;
		}

		if (dialog.widgets.lef2Nucleus.text === undefined || dialog.widgets.lef2Nucleus.text === "") {
			dialog.widgets.lef2Nucleus.setFocus();
			MessageBox.critical("The f2 nucleus parameter is mandatory and has not been specified");
			continue;
		} else if (!SiemensSpectro_Engine.isValidNucleus(dialog.widgets.lef2Nucleus.text)) {
			dialog.widgets.lef2Nucleus.setFocus();
			MessageBox.critical("The f2 nucleus (" + dialog.widgets.lef2Nucleus.text + ") is not a valid nucleus: available nucelus are: " + SiemensSpectro_Engine.validNucleus);
			continue;
		}

		if (dialog.widgets.spbxf1Width.value === 0) {
			dialog.widgets.spbxf1Width.setFocus();
			MessageBox.critical("The f1 width parameter value must not be 0");
			continue;
		}

		if (dialog.widgets.spbxf1Frequency.value === 0) {
			dialog.widgets.spbxf1Frequency.setFocus();
			MessageBox.critical("The f1 frequency value must not be 0");
			continue;
		}

		if (dialog.widgets.lef1Nucleus.text === undefined || dialog.widgets.lef1Nucleus.text === "") {
			dialog.widgets.lef1Nucleus.setFocus();
			MessageBox.critical("The f1 nucleus parameter is mandatory and has not been specified");
			continue;
		} else if (!SiemensSpectro_Engine.isValidNucleus(dialog.widgets.lef1Nucleus.text)) {
			dialog.widgets.lef1Nucleus.setFocus();
			MessageBox.critical("The f1 nucleus (" + dialog.widgets.lef1Nucleus.text + ") is not a valid nucleus: available nucelus are: " + SiemensSpectro_Engine.validNucleus);
			continue;
		}

		showDialog = false;
	}

	dir = new Dir(lastDir);
	if (dir.exists) {
		filenames = dir.entryListAdv("*" + spectroExtension);
		if (filenames.length > 0) {

			//Create the parameters which will be sent to the engine
			parameters = {};
			parameters.f2Width = dialog.widgets.spbxf2Width.value;
			parameters.f2LowestFrequency = dialog.widgets.spbxf2LowestFrequency.value;
			parameters.f2Frequency = dialog.widgets.spbxf2Frequency.value;
			parameters.f2Nucleus = dialog.widgets.lef2Nucleus.text;
			parameters.f1Width = dialog.widgets.spbxf1Width.value;
			parameters.f1LowestFrequency = dialog.widgets.spbxf1LowestFrequency.value;
			parameters.f1Frequency = dialog.widgets.spbxf1Frequency.value;
			parameters.f1Nucleus =  dialog.widgets.lef1Nucleus.text;

			//Create an instance of the engine
			engine = new SiemensSpectro_Engine(parameters);
			results = engine.process(filenames);
			if (results.status === SiemensSpectro_Engine.status.FAILED) {
				MessageBox.critical("The import process has failed");
			} else if (results.status === SiemensSpectro_Engine.status.WITH_ERRORS) {
				MessageBox.critical("The import process has been done with errors");
			} else if (results.status === SiemensSpectro_Engine.status.CANCELLED) {
				MessageBox.critical("The import process has been cancelled");
			}

		} else {
			MessageBox.critical("No spectro files found in the specified directory:\n" + lastDir);
		}
	} else {
		MessageBox.critical("The specified directory does not exists any longer:\n" + lastDir);
	}

	//Save settings to the registry
	settings.setValue(lastDirectoryKey, lastDir);
	settings.setValue(f2WidthKey, dialog.widgets.spbxf2Width.value);
	settings.setValue(f2LowestFrequencyKey, dialog.widgets.spbxf2LowestFrequency.value);
	settings.setValue(f2FrequencyKey, dialog.widgets.spbxf2Frequency.value);
	settings.setValue(f2NucleusKey, dialog.widgets.lef2Nucleus.text);
	settings.setValue(f1WidthKey, dialog.widgets.spbxf1Width.value);
	settings.setValue(f1LowestFrequencyKey, dialog.widgets.spbxf1LowestFrequency.value);
	settings.setValue(f1FrequencyKey, dialog.widgets.spbxf1Frequency.value);
	settings.setValue(f1NucleusKey, dialog.widgets.lef1Nucleus.text);
}

/*****************************MAIN CLASS********************************
************************************************************************/
function importSiemensSpectro() {
	'use strict';

	var viewer;

	//Instance the script by using the Application global variable
	try {
		viewer = new SiemensSpectro_Viewer();
	} catch (Err) {
		MessageBox.critical(Err);
	}

	return viewer;
}

if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_ImportSiemensSpectro = importSiemensSpectro;
}
